using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using UMC.ITME;
using UMC.ITME.Entities;
using UMC.Net;

namespace UMC.Bridge;


public class HttpBridge : INetBridgeReader, IDisposable
{
    Channel<Tuple<long, int>> channel;
    async void Receive(ChannelReader<Tuple<long, int>> reader)
    {

        var buffer = ArrayPool<byte>.Shared.Rent(500);
        try
        {
            while (await reader.WaitToReadAsync())
            {
                if (reader.TryRead(out var result))
                {
                    var timeOut = new CancellationTokenSource(5000);
                    using (var view = NetCache.Share.CreateAccessor(result.Item1, result.Item2))
                    {
                        int size = 0;
                        while ((size = view.Read(buffer, 0, buffer.Length)) > 0)
                        {
                            await _stream.WriteAsync(buffer, 0, size, timeOut.Token);
                        }
                    }


                }
            }
        }
        catch //(Exception ex)
        {

        }
        finally
        {
            ArrayPool<byte>.Shared.Return(buffer);
        }
    }
    Stream _stream;
    private HttpBridge(Stream stream)
    {
        this._stream = stream;

    }

    protected long _InputSize, _OutputSize;
    bool _IsOver;
    public bool IsOver => _IsOver;


    public void Receive(byte[] buffer, int offset, int size)
    {
        if (channel == null)
        {
            channel = Channel.CreateUnbounded<Tuple<long, int>>(new UnboundedChannelOptions()
            {
                SingleReader = true,
                SingleWriter = true
            });
            Receive(channel.Reader);
        }
        if (channel.Writer.TryWrite(Tuple.Create(NetCache.Share.Append(buffer, offset, size), size)) == false)
        {
            Over();

        }
        _OutputSize += size;

    }

    void Over()
    {
        _IsOver = true;
    }

    static HttpBridge()
    {
        BridgeSiteCache = new Data.Caches.Cache("Time", 1200, new BridgeSite(), "Key");
        BridgeConfigCache = new Data.Caches.Cache("Time", 1200, new BridgeConfig(), "Domain");
    }
    static async void PostTraffic(BridgeSite traffic, int BridgeKey)
    {
        var webR = HttpBridgeServier.Sign(new Uri(APIProxy.Uri, $"SiteConfig").WebRequest());
        using (var memory = webR.Memory)
        {
            var data = Utility.AppendDictionary(traffic);
            data["type"] = "Bridge";
            data["Key"] = traffic.Key;
            data["Distributor"] = BridgeKey;
            data["InputSize"] = BitConverter.ToInt64(traffic.InputSize.Span);
            data["OutputSize"] = BitConverter.ToInt64(traffic.OutputSize.Span);

            webR.ContentType = UMC.Net.StringValue.ContentTypeJson;
            webR.Method = HttpMethod.PUT;
            memory.Temp(UMC.Data.JSON.Serialize(data));
            try
            {
                await NetAsync(webR, memory.Temp());
            }
            catch { }
        }
    }
    public static UMC.Data.Caches.Cache BridgeSiteCache;
    public static UMC.Data.Caches.Cache BridgeConfigCache;
    public static BridgeConfig Config(UMC.Web.WebMeta siteD, StringValue domain)
    {
        var site = new BridgeConfig
        {
            Key = Utility.Parse(siteD["Key"], 0),
            Domain = new StringValue(siteD["Domain"]),
            Caption = siteD["Caption"],
            BridgeKey = Utility.Parse(siteD["BridgeKey"], 0),
            AllowSize = Utility.IntParse(siteD["AllowSize"], 0)
        };

        site.Time = new StringValue(BitConverter.GetBytes(Utility.TimeSpan()));
        BridgeConfigCache.Put(site);

        if (domain.Span.SequenceEqual(site.Domain.Span) == false)
        {
            site.Domain = domain;
            BridgeConfigCache.Put(site);
        }
        return site;
    }
    public static async Task<Mime> GetAsync(NetHttpRequest request)
    {
        var memory = request.Memory;
        TcpClient client = new TcpClient();
        await client.ConnectAsync(request.Address.Host, request.Address.Port);
        Stream stream;
        if (request.Address.Scheme == "https")
        {
            SslStream ssl = new SslStream(client.GetStream(), false, (a, b, c, d) => true);
            await ssl.AuthenticateAsClientAsync(request.Address.Host, new X509CertificateCollection(), SslProtocols.None, false);
            stream = ssl;
        }
        else
        {
            stream = client.GetStream();
        }

        NetHttpResponse.Header(request, (b, c, t) =>
        {
            memory.Temp(b.AsSpan(c, t));
        });
        await stream.WriteAsync(memory.Temp().Memory);

        memory.Reset();

        var b = Mime.CreateMime(memory);
        var buffer = System.Buffers.ArrayPool<byte>.Shared.Rent(0x200);
        try
        {
            int l = 0;
            while (b.IsMimeFinish == false && b.IsHttpFormatError == false && (l = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
            {
                b.Receive(buffer, 0, l);
            }
        }
        finally
        {
            System.Buffers.ArrayPool<byte>.Shared.Return(buffer);
        }
        return b;
    }
    public static async Task<Mime> NetAsync(NetHttpRequest request, StringValue value)
    {

        var memory = request.Memory;
        TcpClient client = new TcpClient();
        await client.ConnectAsync(request.Address.Host, request.Address.Port);
        Stream stream;
        if (request.Address.Scheme == "https")
        {
            SslStream ssl = new SslStream(client.GetStream(), false, (a, b, c, d) => true);
            await ssl.AuthenticateAsClientAsync(request.Address.Host, new X509CertificateCollection(), SslProtocols.None, false);
            stream = ssl;
        }
        else
        {
            stream = client.GetStream();
        }
        request.ContentLength = value.Length;

        NetHttpResponse.Header(request, (b, c, t) =>
        {
            memory.Temp(b.AsSpan(c, t));
        });
        await stream.WriteAsync(memory.Temp().Memory);
        await stream.WriteAsync(value.Memory);

        memory.Reset();

        var b = Mime.CreateMime(memory);
        var buffer = System.Buffers.ArrayPool<byte>.Shared.Rent(0x200);
        try
        {
            int l = 0;
            while (b.IsMimeFinish == false && b.IsHttpFormatError == false && (l = await stream.ReadAsync(buffer, 0, buffer.Length)) > 0)
            {
                b.Receive(buffer, 0, l);
            }
        }
        finally
        {
            System.Buffers.ArrayPool<byte>.Shared.Return(buffer);
        }
        return b;
    }

    public static async Task<BridgeConfig> Bridge(StringValue bridgeKey)
    {
        if (bridgeKey.Span.EndsWith(UMC.ITME.WebServlet.MainDomainValue.Span) == false)
        {
            if (bridgeKey.Contains("."u8) == false)
            {
                var value = new byte[UMC.ITME.WebServlet.MainDomainValue.Length + bridgeKey.Length + 1];
                bridgeKey.Span.CopyTo(value);
                value[bridgeKey.Length] = (byte)UMC.ITME.WebServlet.DomainUnion;

                UMC.ITME.WebServlet.MainDomainValue.Span.CopyTo(value.AsSpan(bridgeKey.Length + 1));
                bridgeKey = new StringValue(value);
            }
            else
            {
                return null;
            }
        }
        var config = BridgeConfigCache.Get(new BridgeConfig { Domain = bridgeKey });
        if (config != null)
        {
            if (config.Key == 0)
            {
                if (BitConverter.ToUInt32(config.Time.Span) + 600 > Utility.TimeSpan())
                {
                    return null;
                }
            }
            else
            {
                return config;
            }
        }
        var webr = UMC.Web.WebResource.Instance() as UMC.ITME.WebResource;
        var siteD = await webr.GetBridgeConfig(bridgeKey);
        if (siteD != null)
        {
            return Config(siteD, bridgeKey);
        }

        else
        {
            BridgeConfigCache.Put(new BridgeConfig { Key = 0, Cache = StringValue.Empty, Domain = bridgeKey, Time = new StringValue(BitConverter.GetBytes(Utility.TimeSpan())) });
        }
        return null;
    }

    public static async void Bridge(ITME.TlsSite tlsSite, Stream stream, Socket socket, StringValue ip)
    {
        try
        {
            var bridgeConfig = await Bridge(tlsSite.Host);
            if (bridgeConfig != null)
            {
                if (bridgeConfig.AllowSize <= 0 && tlsSite.Host.Span.StartsWith(Utility.Parse36Encode(bridgeConfig.Key.Value).UTF8()) == false)
                {
                    await BridgeHtml(404, bridgeConfig, stream);
                }
                else if (HttpBridgeServier.CheckBridgeServer(bridgeConfig.Key.Value, out var bridgeServier))
                {
                    await HttpBridge.HttpBridgeAsync(bridgeConfig.Key.Value, bridgeServier, stream, tlsSite.Host, tlsSite.Fingerprint, ip);
                }
                else
                {
                    await BridgeHtml(404, bridgeConfig, stream);
                }
            }

        }
        catch { }
        finally
        {
            stream.Close();
            stream.Dispose();
            socket.Close();
            socket.Dispose();
        }
    }
    static async Task HttpBridgeAsync(int configKey, HttpBridgeServier bridgeServier, Stream stream, StringValue host, StringValue finder, StringValue ip)
    {
        using (stream)
        {
            HttpBridge httpBridge = null;
            var buffer = System.Buffers.ArrayPool<byte>.Shared.Rent(512);
            try
            {
                buffer[9] = 0x02;
                buffer[10] = (byte)host.Length;
                host.Span.CopyTo(buffer.AsSpan(11));
                var size = host.Length + 11;
                buffer[size] = (byte)finder.Length;

                finder.Span.CopyTo(buffer.AsSpan(size + 1));
                size += finder.Length + 1;
                buffer[size] = (byte)ip.Length;
                ip.Span.CopyTo(buffer.AsSpan(size + 1));
                size += ip.Length + 1;
                httpBridge = new HttpBridge(stream);

                int pid = HttpBridgeServier.IncrementId;
                buffer[0] = 0x05;
                BitConverter.TryWriteBytes(buffer.AsSpan(1), pid);
                BitConverter.TryWriteBytes(buffer.AsSpan(5), size - 9);
                bridgeServier.Clients.TryAdd(pid, httpBridge);
                bridgeServier.Bridge(buffer, 0, size);

                buffer[0] = 0x02;
                int bytesRead;

                var timeOut = new CancellationTokenSource(10000);
                while ((bytesRead = await stream.ReadAsync(buffer, 9, buffer.Length - 9, timeOut.Token)) > 0)
                {
                    BitConverter.TryWriteBytes(buffer.AsSpan(5, 4), bytesRead);
                    bridgeServier.Bridge(buffer, 0, bytesRead + 9);
                    timeOut = new CancellationTokenSource(10000);
                }
                httpBridge._IsOver = true;
                BitConverter.TryWriteBytes(buffer.AsSpan(5, 4), 0);
                bridgeServier.Bridge(buffer, 0, 9);
                bridgeServier.Clients.TryRemove(pid, out var _);
                Statistical(configKey, httpBridge._InputSize, httpBridge._OutputSize);
            }
            catch { }
            finally
            {
                System.Buffers.ArrayPool<byte>.Shared.Return(buffer);
                httpBridge?.Over();
                httpBridge?.channel?.Writer?.TryComplete();

            }
        }
    }
    public static void Statistical(int ConfigKey, long InputSize, long OutputSize)
    {
        var Distributor = Utility.IntParse(UMC.ITME.WebServlet.AppId.Span);
        if (Distributor != ConfigKey)
        {
            var traffic = BridgeSiteCache.Get(new BridgeSite { Key = ConfigKey });

            if (traffic == null)
            {
                BridgeSiteCache.Put(new BridgeSite()
                {
                    Key = ConfigKey,
                    InputSize = new StringValue(BitConverter.GetBytes(InputSize)),
                    OutputSize = new StringValue(BitConverter.GetBytes(OutputSize)),
                    Time = new StringValue(BitConverter.GetBytes(Utility.TimeSpan()))
                });
            }
            else
            {
                var inputSize = BitConverter.ToInt64(traffic.InputSize.Span) + InputSize;
                var outputSize = BitConverter.ToInt64(traffic.OutputSize.Span) + OutputSize;

                BitConverter.TryWriteBytes(traffic.InputSize.Span, inputSize);
                BitConverter.TryWriteBytes(traffic.OutputSize.Span, outputSize);

                PostTraffic(traffic, Distributor);
                BitConverter.TryWriteBytes(traffic.InputSize.Span, 0L);
                BitConverter.TryWriteBytes(traffic.OutputSize.Span, 0L);
            }
        }
    }

    static async Task BridgeHtml(int stateCode, BridgeConfig config, Stream stream)
    {

        var traffic = BridgeSiteCache.Get(new BridgeSite { Key = config.Key });
        if (traffic != null)
        {
            var inputSize = BitConverter.ToInt64(traffic.InputSize.Span);
            var outputSize = BitConverter.ToInt64(traffic.OutputSize.Span);//+ this._OutputSize;
            if (inputSize > 0 && outputSize > 0)
            {
                PostTraffic(traffic, config.BridgeKey ?? 0);
                BitConverter.TryWriteBytes(traffic.InputSize.Span, 0L);
                BitConverter.TryWriteBytes(traffic.OutputSize.Span, 0L);
            }
        }
        var sb = new System.Text.StringBuilder();
        sb.Append("<script type=\"text/plain\" id=\"Host\">");

        var webr = UMC.Web.WebResource.Instance() as UMC.ITME.WebResource;
        var tConfig = await webr.GetBridgeConfig(config.Domain);
        if (tConfig != null)
        {
            var site = Config(tConfig, config.Domain);

            sb.Append("{");
            sb.AppendFormat("\"Key\":\"{0}\",", site.Key);
            sb.AppendFormat("\"AllowSize\":\"{0}\",", tConfig["allowSize"]);
            sb.AppendFormat("\"InputSize\":\"{0}\",", tConfig["inputSize"]);
            sb.AppendFormat("\"OutputSize\":\"{0}\",", tConfig["outputSize"]);
            sb.AppendFormat("\"ExpireTime\":\"{0}\",", tConfig["expireTime"]);
            sb.AppendFormat("\"Domain\":\"{0}\",", site.Domain);
            sb.AppendFormat("\"AuthKey\":\"{0}\",", tConfig["AuthKey"]);
            sb.AppendFormat("\"Caption\":\"{0}\",", String.IsNullOrEmpty(site.Caption) ? "未登记" : site.Caption);
            sb.AppendFormat("\"Status\":\"{0}\"", HttpBridgeServier.CheckBridgeServer(site.Key.Value) ? "转发服务在线" : "转发服务不在线");
            sb.Append("}");
        }
        else
        {
            BridgeConfigCache.Delete(new BridgeConfig { Domain = config.Domain });

        }
        sb.Append("</script>");
        await NotFind(stateCode, "bridge", stream, sb.ToString().UTF8());




    }
    public static async Task NotFind(int stateCode, String key, Stream outStream, params byte[] script)
    {
        var path = $"/UI/repost.{key}.html";
        var file = UMC.Data.Utility.FilePath(UMC.Data.Reflection.ConfigPath($"Static/{path}"));
        if (System.IO.File.Exists(file))
        {
            using (System.IO.Stream stream = System.IO.File.OpenRead(file))
            {

                var buffer = System.Buffers.ArrayPool<byte>.Shared.Rent(512);

                try
                {
                    await outStream.WriteAsync(buffer, 0, $"HTTP/1.1 {stateCode} {UMC.Net.HttpStatusDescription.Get(stateCode)}\r\nConnection: close\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: {stream.Length + script.Length}\r\n\r\n".WriteBytes(buffer, 0));
                    // await outStream.FlushAsync();
                    int len = 0;
                    while ((len = stream.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        if (script.Length > 0)
                        {
                            var v = buffer.AsSpan(0, len).IndexOf("</head>"u8);
                            if (v > 0)
                            {
                                await outStream.WriteAsync(buffer, 0, v);
                                await outStream.WriteAsync(script, 0, script.Length);
                                await outStream.WriteAsync(buffer, v, len - v);
                                script = Array.Empty<byte>();
                                continue;
                            }
                        }
                        await outStream.WriteAsync(buffer, 0, len);

                    }
                    // await outStream.FlushAsync();
                }
                catch (Exception ex)
                {
                    Utility.Error("NotFind", ex.ToString());
                }
                finally
                {
                    System.Buffers.ArrayPool<byte>.Shared.Return(buffer);
                }
            }
        }
        else
        {
            var url = new Uri($"https://res.apiumc.com{path}?v{UMC.Data.Utility.TimeSpan()}");
            var btn = new ByteChunk();
            url.WebRequest(btn).Get(xhr =>
            {
                btn.Close();
                if (xhr.StatusCode == HttpStatusCode.OK)
                {
                    var ms = UMC.Data.Utility.Writer(file, false);
                    xhr.ReadAsData((b, i, c) =>
                    {
                        if (c == 0 && b.Length == 0)
                        {
                            if (i == -1)
                            {
                                ms.Close();
                                ms.Dispose();

                                outStream.Close();
                            }
                            else
                            {
                                ms.Flush();
                                ms.Close();

                            }
                        }
                        else
                        {
                            ms.Write(b, i, c);
                        }
                    });
                }

            });
        }
    }

    public void Dispose()
    {

        channel?.Writer?.TryComplete();
        Over();
    }

    static void Proxy(Socket socket, byte[] header)
    {
        var size = 3;
        var domain = new StringValue(header, 4, header[size]);

        size += header[size] + 1;
        var ip = new StringValue(header, size + 1, header[size]);

        size += header[size] + 1;
        var username = new StringValue(header, size + 1, header[size]);

        // StringValue UserAgent;
        // var proxySite = UMC.Data.HotCache.Find(new ProxySite { Domain = domain }, "Domain", DnsServer.ParentDomain(domain)).FirstOrDefault();
        // if (proxySite != null)
        // {
        //     UserAgent = proxySite?.Domain;
        // }
        // else
        // {

        //     UserAgent = UMC.Data.Utility.IntParse(UMC.Data.Utility.MD5(ip.Span)).StringValue();
        // }
        //    ( ushort)
        UMC.Bridge.SocksUser.Cache.Put(new UMC.Bridge.SocksUser
        {
            ClientIP = Utility.IntParse(Utility.MD5(username.Span)).StringValue(),// username ,
            Port = ((ushort)443).StringValue(),
            UserAgent = ip,// UserAgent,//proxySite?.Domain ?? md5.StringValue(),
            Username = username,// new StringValue($"{username}@{UMC.Data.Utility.Parse36Encode(key)}"),
            ActiveTime = new StringValue(BitConverter.GetBytes(UMC.Data.Utility.TimeSpan())),
            ExpirationTime = new StringValue(BitConverter.GetBytes(UMC.Data.Utility.TimeSpan(DateTime.Now.AddHours(1))))
        });
        socket.Close();
        socket.Dispose();

    }
    static async Task BridgeClient(Socket socket, byte[] header)
    {
        if (header[3] == 0x00)
        {
            header[0] = 0x00;
            header[1] = (byte)"OK".WriteBytes(header, 2);
            header[header[1] + 2] = 0x00;
            await socket.SendAsync(new ArraySegment<byte>(header, 0, header[1] + 3));
            new NetBridgeClient().Bridge(socket);
        }
        else
        {
            var config = await UMC.Bridge.HttpBridge.Bridge(new StringValue(header, 4, header[3]));

            if (config == null)
            {
                header[0] = 0x01;
                header[1] = (byte)"当前并不支持此域名".WriteBytes(header, 2);
                header[header[1] + 2] = 0x00;
                await socket.SendAsync(new ArraySegment<byte>(header, 0, header[1] + 3));
                socket.Close();
                socket.Dispose();
                return;
            }
            header[0] = 0x00;
            header[1] = (byte)"OK".WriteBytes(header, 2);
            header[header[1] + 2] = 0x00;
            await socket.SendAsync(new ArraySegment<byte>(header, 0, header[1] + 3));
            new NetBridgeClient() { ConfigKey = config.Key.Value }.Bridge(socket);
        }
    }
    static async Task BridgeServie(Socket socket, byte[] header)
    {
        var bridge_number = header[2];
        var config = await UMC.Bridge.HttpBridge.Bridge(new StringValue(header, 4, header[3]));

        if (config == null)
        {
            header[0] = 0x01;
            header[1] = (byte)"当前并不支持此域名".WriteBytes(header, 2);
            header[header[1] + 2] = 0x00;
            await socket.SendAsync(new ArraySegment<byte>(header, 0, header[1] + 3));
            socket.Close();
            socket.Dispose();
            return;
            // config.
        }
        if (bridge_number == 0xff)
        {
            UMC.Bridge.HttpBridgeServier.CloseBridgeServer(config.Key.Value);
            bridge_number = 0;
        }
        UMC.Bridge.HttpBridgeServier.RemoveBridgeServer(config.Key.Value, bridge_number);

        var total = UMC.Bridge.HttpBridgeServier.GetBridgeServers(config.Key.Value);

        if (total > 4)
        {
            header[0] = 0x01;
            header[1] = (byte)"穿透连接通道已经超过数量".WriteBytes(header, 2);
            header[header[1] + 2] = 0x00;
            await socket.SendAsync(new ArraySegment<byte>(header, 0, header[1] + 3));
            socket.Close();
            socket.Dispose();
        }
        else
        {

            header[0] = 0x00;
            header[1] = (byte)"OK".WriteBytes(header, 2);
            header[header[1] + 2] = 0x00;

            await socket.SendAsync(new ArraySegment<byte>(header, 0, header[1] + 3));

            var bs = new UMC.Bridge.HttpBridgeServier(bridge_number);
            bs.Bridge(socket);
            UMC.Bridge.HttpBridgeServier.AppendServer(config.Key.Value, bs);
        }
    }
    public static async Task Bridge(Socket socket, Memory<byte> headerValue)
    {
        int size = headerValue.Length;
        var header = System.Buffers.ArrayPool<byte>.Shared.Rent(0x200);
        headerValue.CopyTo(header);
        try
        {
            int len = 3;

            len += header[len] + 1;
            if (len > size)
            {
                var timeOut = new CancellationTokenSource(5000);
                size += await socket.ReceiveAsync(new ArraySegment<byte>(header, size, header.Length - size), timeOut.Token);
            }

            switch (header[1])
            {
                case 0x04:
                    Proxy(socket, header);
                    break;
                case 0x03:
                    await Subscribe(socket, header);
                    break;
                case 0x01:
                    await BridgeClient(socket, header);
                    break;
                default:
                    await BridgeServie(socket, header);
                    break;
            }
        }
        finally
        {
            System.Buffers.ArrayPool<byte>.Shared.Return(header);
        }
    }

    static async Task Subscribe(Socket client, byte[] header)
    {
        try
        {
            var phKey = header.AsMemory(4, header[3]);
            var size = header[3] + 4;
            var sign = header.AsSpan(size + 1, header[size]).ToArray();

            WebServlet.AppSecret.Span.CopyTo(header.AsSpan(size));

            size += WebServlet.AppSecret.Span.Length;

            var pKey = phKey.Slice(0, phKey.Length - 4).UTF8();// header.AsSpan(2, header[1]).UTF8();
            UMC.Data.Utility.MD5(header.AsSpan(4, size), header.AsSpan(size));
            if (String.Equals(pKey, HttpMimeServier.Server) == false && sign.AsSpan().SequenceEqual(header.AsSpan(size, 16)))
            {
                var ip = (client.RemoteEndPoint as IPEndPoint).Address;
                header[0] = 0x00;
                header[1] = (byte)HttpMimeServier.Server.WriteBytes(header, 2);
                header[header[1] + 2] = 0x00;
                await client.SendAsync(new ArraySegment<byte>(header, 0, header[1] + 3));
                NetSubscribe.Subscribe(pKey, ip.ToString(), HttpMimeServier.Server, client, WebServlet.AppSecret);// UMC.Data.WebResource.Instance().Provider["appSecret"]);

            }
            else
            {
                header[0] = 0x01;
                header[1] = (byte)"数据订阅连接验证不通过".WriteBytes(header, 2);
                header[header[1] + 2] = 0x00;
                await client.SendAsync(new ArraySegment<byte>(header, 0, header[1] + 3));
                client.Close();
                client.Dispose();
            }
        }
        catch
        {
            client.Close();
            client.Dispose();
        }
    }

}
